﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Diagnostics;
using Engine.Dtd.Schema;
using System.Runtime.InteropServices;

namespace Engine.Sgml.Matching {
    /// <summary>
    /// Represents the information about accordance of model groups
    /// with sequence of a document elements.
    /// </summary>
    [DebuggerDisplay("{Type} {StartIndex} - {EndIndex}")]
    internal sealed class Match {
        [DebuggerBrowsable(DebuggerBrowsableState.Never)]
        private DModelGroups model;
        [DebuggerBrowsable(DebuggerBrowsableState.Never)]
        private SElement ancestor;
        [DebuggerBrowsable(DebuggerBrowsableState.Never)]
        private CaptureCollection captures;
        [DebuggerBrowsable(DebuggerBrowsableState.Never)]
        private Match parent;

        public Match(DModelGroups model, SElement ancestor, Match parent) {
            Guard.ArgumentNotNull(model, "model");
            Guard.ArgumentNotNull(ancestor, "ancestor");

            this.model = model;
            this.ancestor = ancestor;
            this.parent = parent;
            this.captures = new CaptureCollection();
            }

        /// <summary>
        /// Gets the type of accordance.
        /// </summary>
        public AccordanceTypes Type {
            get {
                if (model == DModelGroups.Empty || model.Count == 0)
                    return AccordanceTypes.Full;
                if (captures.Count == 0 || !captures.Any(c => c.Success && !c.Entity.IsInclusion))
                    return model.MustPresent() ? AccordanceTypes.Inconsistent : AccordanceTypes.Full;
                if (model.OrderType == DGroupOrderType.OnlyOne) {
                    int index = 0;
                    while (index < captures.Count) {
                        if (captures[index].Success && !captures[index].Entity.IsInclusion)
                            break;
                        index++;
                        }
                    if (index == captures.Count)
                        return model.MustPresent() ? AccordanceTypes.Inconsistent : AccordanceTypes.Full;
                    else
                        for (int i = index + 1; i < captures.Count; i++) {
                            if (captures[i].Success) {
                                CaptureEntity entity = captures[i].Entity;
                                if (!entity.IsInclusion && entity.IsModelMatch && entity.Match.Type == AccordanceTypes.Partial)
                                    return AccordanceTypes.Partial;
                                }
                            }
                    CaptureEntity e = captures[index].Entity;
                    if (e.IsModelMatch)
                        return e.Match.Type;
                    else
                        return AccordanceTypes.Full;
                    }
                if (captures.Any(c => c.Entity.IsModelMatch && c.Entity.Match.Type != AccordanceTypes.Full))
                    return AccordanceTypes.Partial;
                else
                    for (int i = 0; i < model.Count; i++)
                        if (model[i].MustPresent()) {
                            Capture? capture = captures.FindBy(model[i]);
                            if (capture == null || !capture.Value.Success)
                                return AccordanceTypes.Partial;
                            }
                return AccordanceTypes.Full;
                }
            }

        public bool HasMatches {
            get { return (captures.Count > 0 && captures.Any(c => c.Success)); }
            }

        /// <summary>
        /// Gets the model groups which accordance is set for.
        /// </summary>
        public DModelGroups Model { get { return model; } }

        /// <summary>
        /// Gets a collection of all the captures which accordance is set
        /// during an analysis.
        /// </summary>
        public CaptureCollection Captures { get { return captures; } }

        /// <summary>
        /// Gets the parent match of this object.
        /// </summary>
        public Match Parent {
            get { return parent; }
            internal set {
                Guard.ArgumentNotNull(value, "value");
                parent = value;
                }
            }

        internal SElement Ancestor { get { return ancestor; } }

        /// <summary>
        /// Gets the last element which corresponds to a current model groups. 
        /// </summary>
        public SElement EndElement {
            get {
                if (IsInconsistent())
                    return GetElementForInconsistent();
                else {
                    Capture capture = Captures.Last(c => c.Success);
                    if (capture.Entity.IsElement)
                        return capture.Entity.Element;
                    else
                        return capture.Entity.Match.EndElement;
                    }
                }
            }

        /// <summary>
        /// Gets the first element which corresponds to a current model groups.
        /// </summary>
        public SElement StartElement {
            get {
                if (IsInconsistent())
                    return GetElementForInconsistent();
                else {
                    Capture capture = Captures.First(c => c.Success);
                    if (capture.Entity.IsElement)
                        return capture.Entity.Element;
                    else
                        return capture.Entity.Match.StartElement;
                    }
                }
            }

        /// <summary>
        /// Gets an index of the first node which corresponds to a model groups.
        /// </summary>
        public int StartIndex {
            get {
                if (IsInconsistent())
                    return GetIndexForInconsistent();
                else
                    return StartElement.OrderIndex;
                }
            }

        internal int NextIndex {
            get {
                if (IsInconsistent())
                    return GetIndexForInconsistent();
                else
                    return EndElement.OrderIndex + 1;
                }
            }

        /// <summary>
        /// Gets the index of the last node which corresponds to a model groups.
        /// </summary>
        public int EndIndex {
            get {
                if (IsInconsistent())
                    return GetIndexForInconsistent();
                else
                    return EndElement.OrderIndex;
                }
            }

        private int GetIndexForInconsistent() {
            CaptureEntity? previous = GetPreviousCaptureEntity();
            if (previous == null)
                return 0;
            if (previous.Value.IsElement)
                return previous.Value.Element.OrderIndex + 1;
            else
                return previous.Value.Match.NextIndex;
            }

        private SElement GetElementForInconsistent() {
            CaptureEntity? previous = GetPreviousCaptureEntity();
            if (previous == null)
                return null;
            if (previous.Value.IsElement)
                return previous.Value.Element;
            else
                return previous.Value.Match.EndElement;
            }

        private CaptureEntity? GetPreviousCaptureEntity() {
            Match p = parent;
            Match m = this;
            while (p != null) {
                for (int i = 0; i < p.captures.Count; i++) {
                    if (object.ReferenceEquals(p.captures[i].Entity.Match, m)) {
                        for (int j = i - 1; j >= 0; j--) {
                            if (p.captures[j].Success)
                                return p.captures[j].Entity;
                            }
                        }
                    }
                m = p;
                p = p.parent;
                }
            return null;
            }

        private bool IsInconsistent() {
            return captures.Count == 0 || !captures.Any(c => c.Success);
            }
        }
    }
